#pragma once

#include <MCF/Code/StdMCF.hpp>
#include <vector>

class CPUClass sealed : NO_COPY_OR_ASSIGN {
private:
	static const std::size_t _ROM_SIZE_IN_DWORDS = 4 * 0x400;
	static const std::size_t _RAM_SIZE_IN_DWORDS = 0x80 * 0x400;

	typedef enum _tagRAMAccessResult {
		RA_ADDRESS_NOT_ALIGNED,
		RA_BAD_ADDRESS
	} _RAM_ACCESS_RESULT;

	typedef struct _tagRAMAccessException {
		_RAM_ACCESS_RESULT eResult;
		DWORD dwAddr;

		_tagRAMAccessException(_RAM_ACCESS_RESULT eMyResult, DWORD dwMyAddr){
			eResult = eMyResult;
			dwAddr = dwMyAddr;
		}
	} _RAM_ACCESS_EXCEPTION;

	typedef enum : unsigned int {
		REG_QWORD_RESULT_HI,
		REG_QWORD_RESULT_LO,
		REG_INT_ALLOWED,
		REG_INT_EN_MASK,
		REG_INT_FLAGS,
		REG_INT_RET_ADDR,
		REG_PC,
		REG_OLD_INT_ALLOWED,
		REG_INSTRUCTION_COUNT
	} _REG_INDEX;

	typedef enum : unsigned int {
		INT_SIGNED_ADDSUB_OVERFLOWED,
		INT_SD_REQUEST_COMPLETE,
		INT_KEYBOARD_CODE_AVAILABLE
	} _INTERRUPT_INDEX;

	typedef enum : DWORD {
		FUNC_NOP,
		FUNC_RSC,
		FUNC_CLI,
		FUNC_STI,
		FUNC_IRET
	} _FUNC_TABLE;

	typedef enum : DWORD {
		_OP_FUNC,
		OP_LB,
		OP_LH,
		OP_LW,
		OP_JMP,
		OP_SB,
		OP_SH,
		OP_SW,
		OP_JZ,
		OP_JNZ,
		OP_BEQ,
		OP_BNE,
		OP_BLTU,
		OP_BLT,
		OP_MOVZ,
		OP_GPC,
		OP_ADDIU,
		OP_ADDI,
		OP_SUBIU,
		_OP_RESERVED_0,
		OP_ANDI,
		OP_ORI,
		OP_XORI,
		_OP_RESERVED_1,
		OP_SLL,
		_OP_RESERVED_2,
		OP_SRL,
		OP_SRA,
		_OP_RESERVED_3,
		_OP_RESERVED_4,
		OP_MOV,
		OP_JAL,
		OP_JR,
		OP_LBU,
		OP_LHU,
		_OP_RESERVED_5,
		OP_BNP,
		OP_BPT,
		_OP_RESERVED_6,
		_OP_RESERVED_7,
		OP_MULIU,
		OP_MULI,
		OP_DIVIU,
		OP_DIVI,

		_OP_RFUNC = 0x3F
	} _OP_TABLE;

	typedef enum : DWORD {
		RFUNC_ADDU,
		RFUNC_ADD,
		RFUNC_SUBU,
		RFUNC_SUB,
		RFUNC_AND,
		RFUNC_OR,
		RFUNC_XOR,
		RFUNC_NOT,
		RFUNC_SLLV,
		_RFUNC_RESERVED_0,
		RFUNC_SRLV,
		RFUNC_SRAV,
		RFUNC_SLTU,
		RFUNC_SLT,
		RFUNC_MOV,
		_RFUNC_RESERVED_1,
		RFUNC_MFLO,
		RFUNC_MFLZ,
		_RFUNC_RESERVED_2,
		_RFUNC_RESERVED_3,
		RFUNC_MFHI,
		RFUNC_MFHZ,
		_RFUNC_RESERVED_4,
		_RFUNC_RESERVED_5,
		RFUNC_MULU,
		RFUNC_MUL,
		RFUNC_DIVU,
		RFUNC_DIV,
		RFUNC_MLTU,
		RFUNC_MLT
	} _RFUNC_TABLE;
private:
	static bool _EventKeyboardBufferModifiedProc(std::intptr_t nContext, std::intptr_t nArg1, std::intptr_t nArg2, std::intptr_t nArg3);
	static bool _EventSDRequestCompleteProc(std::intptr_t nContext, std::intptr_t nArg1, std::intptr_t nArg2, std::intptr_t nArg3);

	static bool _EventCPUInterpreteInstructionProc(std::intptr_t nContext, std::intptr_t nArg1, std::intptr_t nArg2, std::intptr_t nArg3);
	static bool _EventCPUQueryRegValueProc(std::intptr_t nContext, std::intptr_t nArg1, std::intptr_t nArg2, std::intptr_t nArg3);

	static bool _EventCPUSnapshotRAMROMProc(std::intptr_t nContext, std::intptr_t nArg1, std::intptr_t nArg2, std::intptr_t nArg3);

	static DWORD _AddSigned(bool *lpbOverflowed, DWORD op1, DWORD op2);
	static DWORD _SubSigned(bool *lpbOverflowed, DWORD op1, DWORD op2);
private:
	DWORD _m_ardwGPRs[64];

	union {
		union {
			struct {
				DWORD lo;
				DWORD hi;
			} ddw;
			UINT64 qw;
		} mul;
		struct {
			UINT32 quo;
			UINT32 rem;
		} div;
	} _m_QWordResult;

	bool _m_bIntAllowed;
	bool _m_bOldIntAllowed;
	DWORD _m_dwIntEnMask;
	DWORD _m_dwIntFlags;
	DWORD _m_dwIntRetAddr;

	DWORD _m_dwPC;

	DWORD _m_dwInstructionCount;

	MCF::CriticalSection _m_csLock;

	BYTE _m_arbyRAM[_RAM_SIZE_IN_DWORDS * sizeof(DWORD)];
	BYTE _m_arbyROM[_ROM_SIZE_IN_DWORDS * sizeof(DWORD)];
	DWORD _m_ardwSDCardBuffer[0x200 / sizeof(DWORD)];

	std::vector<unsigned char> _m_vecuRegInvalidateFlags;
public:
	CPUClass();
	~CPUClass();
private:
	// GPR operations
	DWORD _GGPR(unsigned int uIndex) const;
	void _SGPR(unsigned int uIndex, DWORD dwVal);
	// CR(Control Register) operations
	DWORD _GCR(_REG_INDEX eIndex) const;
	void _SCR(_REG_INDEX eIndex, DWORD dwVal);

	DWORD _FetchInstructionDWord(DWORD dwAddress) const;
	BYTE _LoadByte(DWORD dwAddress);
	WORD _LoadHalf(DWORD dwAddress);
	DWORD _LoadWord(DWORD dwAddress);
	void _SaveByte(BYTE byVal, DWORD dwAddress);
	void _SaveHalf(WORD wVal, DWORD dwAddress);
	void _SaveWord(DWORD dwVal, DWORD dwAddress);

	void _GenInterrupt(_INTERRUPT_INDEX eIntIndex);
	void _ClearInterrupt(_INTERRUPT_INDEX eIntIndex);
	bool _CheckInterrupt(_INTERRUPT_INDEX eIntIndex) const;

	DWORD _QueryRegValue(unsigned int uRegIndex) const;

	bool _IsRAMAddrOutOfRange(DWORD dwAddr, DWORD dwSize) const;
public:
	void Initialize();
	std::size_t GetROMDWordCount() const;
	void SetROMContents(const DWORD *lpdwNewContents, std::size_t uDWordCount);
	void Step();

	void InterpreteInstruction(MCF::TString *lpstrRetAsmCode, DWORD *lpdwRetMachineCode, DWORD dwAddress) const;
	void FlushInvalidatedRegs();

	void SnapshotRAMROM(WORD *lpwRetContents, DWORD dwAddr, DWORD dwSize) const;
};
